
import pyparsing

from pyparsing import Literal, CaselessKeyword, CaselessLiteral, \
     Word, Optional, OneOrMore, Group, Combine, ZeroOrMore, nums, \
     Forward, StringEnd, restOfLine, alphas, alphanums

class SexadecimalCoord(object):

    def __init__(self, s):
        self.string = s
        
        if s[0] == "-":
            _sign = -1
            s=s[1:]
        else:
            _sign = 1

        ss = map(float, s.split(":"))
        if len(ss) == 1:
            v = _sign * ss[0]
        elif len(ss) == 2:
            v = _sign * (ss[0] + ss[1]/60.)
        elif len(ss) == 3:
            v = _sign * (ss[0] + ss[1]/60. + ss[2]/3600.)

        self.degree = v
        self.value = v

    def __repr__(self):
        return "Degree(%s)" % self.string


class Angle(object):

    def __init__(self, s):
        s = s.strip()
        self.string = s
        degree = 0.
        
        ind = s.find("'")
        if ind > 1: # arcminute
            degree += (float(s[:ind])/60.)
            s = s[ind+1:]

        ind = s.find("\"")
        if ind > 1: # arcsec
            degree += (float(s[:ind])/3600.)
            s = s[ind+1:]

        if s:
            raise Exception("wrong format")

        self.degree = degree
        self.value = degree
        
    def __repr__(self):
        return "ArcDeg(%s)" % self.string
    
        
class AnglePart(object):
    def __init__(self, s):
        self.part = s

    def __repr__(self):
        return self.part

class SexadecimalCoordPart(object):
    def __init__(self, s):
        self.part = s
        
    def __repr__(self):
        return self.part
        

class SimpleNumber(object):
    def __init__(self, s):
        self.value = float(s)
        self.string = s
        
    def __repr__(self):
        return "PureNumber(%s)" % self.string
    
def number():
    def simple_number():
        # fnumber : 102.43, 12304.3e10,
        #           .32???
        point = Literal( "." )
        e     = CaselessLiteral( "E" )
        fnumber = Combine( Word( "+-"+nums, nums ) + 
                           Optional( point + Optional( Word( nums ) ) ) +
                           Optional( e + Word( "+-"+nums, nums ) ) )
        #fnumber = _fnumber.setParseAction( lambda s,l,t: [ float(t[0]) ] )
        return fnumber


    def sexadecimal_part():
        # fnumber : 10:32:42.43, -32:32:54.3
        #           .32???

        point = Literal( "." )
        unsigend_float = Combine( Word(nums) + 
                                   Optional( point +
                                             Optional( Word( nums ) ) ) )

        _s = Combine(":" + unsigend_float + Optional(":" + unsigend_float))

        #s = _s.setParseAction( lambda s,l,t: [ sexagecimal(t[0]) ] )
        s = _s
        return s


    def sph_angle_part():
        # fnumber : 10' 32"

        point = Literal( "." )
        unsigend_float = Combine( Word(nums) + 
                                   Optional( point +
                                             Optional( Word( nums ) ) ) )

        _sec = Literal('"')
        #_sec.setParseAction(lambda s,l,t: float(t[0])/3600.)

        _min = Literal("'")
        #_min.setParseAction(lambda s,l,t: float(t[0])/60.)
        
        _s =  _sec | (_min + Optional(unsigend_float + _sec))

        #s = _s.setParseAction( lambda s,l,t: [ spherical_angle(sum(t[:])) ] )
        s = Combine(_s)
        
        return s


    def defineSimpleNumber(s, l, tok):
        return SimpleNumber(tok[0])

    def defineSexadecimalCoordPart(s, l, tok):
        return SexadecimalCoordPart(tok[0])

    def defineAnglePart(s, l, tok):
        return AnglePart(tok[0])

    def defineNumber(s, l, tok):
        if len(tok) == 1:
            return tok[0]
        elif len(tok) == 2:
            if isinstance(tok[1], AnglePart):
                return Angle(repr(tok[0]) + repr(tok[1]))
            if isinstance(tok[1], SexadecimalCoordPart):
                return SexadecimalCoord(repr(tok[0]) + repr(tok[1]))
        
        return SimpleNumber(tok[0])
    
    sn = simple_number().setParseAction(defineSimpleNumber)
    se = sexadecimal_part().setParseAction(defineSexadecimalCoordPart)
    sp = sph_angle_part().setParseAction(defineAnglePart)

    
    s = (sn + Optional( se | sp )).setParseAction(defineNumber)

    return s


def test_number():
    n = number()
    teststring = ["32.2",
                  "-13.4",
                  "13.4e5",
                  "-13.4e5",
                  "13.4e-5",
                  "+13.4e-5",
                  "13'43\"",
                  "+13:32:32.3",
                  ]

    for s in teststring:
        print n.parseString(s)

    
if __name__ == "__main__":
    test_number()
    
